/* 
 * Copyright 2009 Shared Object (www.sharedobject.ch)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package edos.prm.event;

import java.util.Date;
import java.util.HashSet;

import edos.prm.activity.Activity;
import edos.prm.activity.ActivityManager.PrmActivityManager;
import edos.prm.artifact.Artifact;
import edos.prm.artifact.ArtifactManager;
import edos.prm.artifact.AttributePair;
import edos.prm.artifact.PrmRuntimeException;
import edos.prm.artifact.UnregisteredArtifactType;
import edos.prm.artifact.ArtifactManager.PrmArtifactManager;
import edos.prm.util.Time;
import edos.prm.util.VersionNumber;
import edos.prm.engine.PRM;
import edos.prm.engine.PrmSecurityManager;

/**
 * The {@code EventManager} activity deals with the management of
 * {@code Event}s in a project.
 * <p>
 * Events are asynchronous communication objects for processes. A process that
 * raises an event does not wait for the event to be noticed by another process.
 * A process that observes an event blocks until it observes the event that it
 * is waiting for.
 * 
 * @version 1.0
 */
public interface EventManager extends Activity {

	/**
	 * Observe an event with specific attributes.
	 * <p>
	 * The call blocks until an event appears with the specified attributes.
	 * If more than one event exists with these attributes, then one of the
	 * events is chosen arbitrarily. If more than one process attempt to
	 * observe an event at the same time, then only one of the processes
	 * succeeds. An event disappears from the universe of raised events once
	 * it has been observed by a process.
	 * 
	 * @param attributes a list of attribute pairs describing the event to
	 * observe.
	 * @return an event whose attributes match with those specified in the
	 * arguments
	 * @throws RoleException if the task does not possess the right to execute
	 * this operation.
	 */
	public Event observe(AttributePair<?>...attributes);

	/**
	 * Raise an event in the space of executing processes (tasks).
	 * <p>
	 * This call blocks any process waiting on event with the attributes that
	 * the raised event possesses.
	 * 
	 * @param event the event to signal
	 * @throws RoleException if the task does not possess the right to execute
	 * this operation.
	 */
	public void raiseEvent(Event event);

	/**
	 * Create a new event of a specific type.
	 * <p>
	 * The event gets created, though does not yet get "raised". The raiseEvent
	 * operation is responsible for rendering the event visible to the set of
	 * executing processes that are awaiting the appearance of events.
	 * <p>
	 * The creation of an event with the {@code newEvent} method has the same
	 * effect as the {@code newArtifact} method of {@code ArtifactManager}.
	 * 
	 * @param type the name of the event type for which an instance is created
	 * @param args the arguments to the new event object
	 * @throws UnregisteredArtifactType if the type has not been registered
	 * with the PRM.
	 * @throws IllegalArgumentException if type does not represent an event
	 * type.
	 * @throws RoleException if the task does not possess the right to execute
	 * this operation.
	 */
	public Event newEvent(Class<? extends Event> type, Object...args)
	throws UnregisteredArtifactType;

	/**
	 * Default Java implementation of the {@code EventManager} activity.
	 */
	static class PrmEventManager implements EventManager {

		private static PrmEventManager instance;
		private HashSet<Event> raisedEvents;

		static {
			instance = new PrmEventManager();
		}

		public static PrmEventManager getInstance() {
			return instance;
		}

		private Time creationDate;
		private VersionNumber version;
		private String description;

		private PrmEventManager() {
			this.creationDate = new Time(new Date());
			this.version = PRM.getPRMVersion();
			this.description = "Default EventManager Implementation";
			this.raisedEvents = new HashSet<Event>();
			PrmActivityManager.getInstance().register(this);
		}

		public synchronized void raiseEvent(Event event) {
			PrmSecurityManager.roleCheck("raiseEvent", getClass(), Event.class);
			raisedEvents.add(event);
			notifyAll();
		}

		public synchronized Event observe(AttributePair<?>...attributes) {
			PrmSecurityManager.
			roleCheck("observe", getClass(), AttributePair[].class);
			
			ArtifactManager am = PrmArtifactManager.getInstance();
			while ( true ) {
				try {
					for ( Event event : this.raisedEvents ) {
						if ( am.match(event, attributes) ) {
							this.raisedEvents.remove(event);
							am.removeArtifact(event);
							return event;
						}
					}
					// No event found so sleep
					wait();
				} catch (Exception e) {
					e.printStackTrace();
					throw new PrmRuntimeException("Observe");
				}
			}
		}

		public Event newEvent(Class<? extends Event> type, Object... args)
		throws UnregisteredArtifactType {
			PrmSecurityManager.
			roleCheck("newEvent", getClass(),
					new Class[] {Class.class, Object[].class});
			Artifact art = 
				PrmArtifactManager.getInstance().newArtifact(type, args);
			return (Event)art;
		}

		public Time getCreationDate() {
			return creationDate;
		}

		public String getDescription() {
			return description;
		}

		public VersionNumber getVersion() {
			return version;
		}

	}
}
